﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Win32;

namespace ResourceBlender_Express {
    /// <summary>
    /// A dialog for importing from existing resource files, XML spreadsheets and XML backups.
    /// </summary>
    public partial class WinImport : Window {
        private Dictionary<string, string> files = new Dictionary<string, string>();
        private Style styError,
                      stySuccess,
                      styStatus;
        private BackgroundWorker worker;
        private Constants.DataType type;

        /// <summary>
        /// Initializes a new <see cref="WinImport"/>.
        /// </summary>
        /// <param name="type">The type.</param>
        public WinImport(Constants.DataType type) {
            InitializeComponent();
            this.type = type;
            styError = this.FindResource("ErrorStyle") as Style;
            stySuccess = this.FindResource("SuccessStyle") as Style;
            styStatus = this.FindResource("StatusStyle") as Style;
        }

        /// <summary>
        /// Loads the list of applications and pre-selects a type
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            lstType.SelectedValue = type.ToString().ToLower();
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                Database db = new Database();
                try {
                    dwe.Result = db.LoadApplications(false);
                } catch(Exception ex) {
                    dwe.Result = ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null);
                if(rwe.Result != null && rwe.Result is IEnumerable<RBApplication>) {
                    lstApplications.ItemsSource = rwe.Result as IEnumerable<RBApplication>;
                } else {
                    DialogBox.ShowAlert(this, "Error loading applications: " + rwe.Result.ToString(), "Error");
                }
            };
            SetStatus("Loading applications...");
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Determines whether the current page can be changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanMovePage(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = worker == null || !worker.IsBusy;
        }

        /// <summary>
        /// Moves to the previous page.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void MovePreviousPage(object sender, RoutedEventArgs e) {
            switch((tabWizard.SelectedItem as TabItem).Name.ToLower()) {
                case "tbxml":
                case "tbxmlxls":
                case "tbresources":
                    btnPrevious.Visibility = Visibility.Hidden;
                    btnNext.Content = "Next";
                    tbStart.IsSelected = true;
                    break;
                case "tbprogress":
                    btnPrevious.Visibility = Visibility.Visible;
                    btnNext.Content = "Import";
                    switch(type) {
                        case Constants.DataType.XML:
                            tbXML.IsSelected = true;
                            break;
                        case Constants.DataType.XMLXLS:
                            tbXMLXLS.IsSelected = true;
                            break;
                        default:
                            tbResources.IsSelected = true;
                            break;
                    }
                    break;
            }
        }

        /// <summary>
        /// Moves to the next page or finishes the wizard if the last page is active.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void MoveNextPage(object sender, RoutedEventArgs e) {
            btnPrevious.Visibility = Visibility.Visible;
            switch((tabWizard.SelectedItem as TabItem).Name.ToLower()) {
                case "tbstart":
                    txtFilename.GetBindingExpression(TextBox.TextProperty).UpdateSource();
                    if(ValidateFilename()) {
                        PromptSelectComponents();
                    }
                    break;
                case "tbxml":
                    if(chkApplications.IsChecked != true && chkElements.IsChecked != true && chkResources.IsChecked != true && chkTemplates.IsChecked != true) {
                        DialogBox.ShowAlert(this, "Nothing selected to import", "Error");
                    } else {
                        tbProgress.IsSelected = true;
                        prgProgress.Visibility = Visibility.Hidden;
                        btnNext.Content = "Next";
                        DoImport();
                    }
                    break;
                case "tbxmlxls":
                    tbProgress.IsSelected = true;
                    prgProgress.Visibility = Visibility.Hidden;
                    DoImport();
                    btnNext.Content = "Next";
                    break;
                case "tbresources":
                    if(lstApplications.SelectedValue == null) {
                        DialogBox.ShowAlert(this, "An application must be selected", "Error");
                    } else {
                        tbProgress.IsSelected = true;
                        prgProgress.Visibility = Visibility.Hidden;
                        DoImport();
                        btnNext.Content = "Next";
                    }
                    break;
                case "tbprogress":
                    DialogResult = true;
                    Close();
                    break;
            }
            Properties.Settings.Default.Save();
        }

        private void BrowseInputFile(object sender, ExecutedRoutedEventArgs e) {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.DefaultExt = ".xml";
            dlg.CheckPathExists = true;
            dlg.FileName = txtFilename.Text;
            dlg.Filter = "All files|*.*|Zip Files (.zip)|*.zip|ResourceBlender XML Backups (.xml)|*.xml";
            dlg.Title = "Choose file";
            if(dlg.ShowDialog() == true) {
                txtFilename.Text = dlg.FileName;
            }
        }

        /// <summary>
        /// Validates the filename entered.
        /// </summary>
        /// <returns><c>true</c> if the filename is valid</returns>
        private bool ValidateFilename() {
            bool valid = true;
            if(string.IsNullOrEmpty(txtFilename.Text)) {
                DialogBox.ShowAlert(this, "Filename to import must be specified", "Error");
                valid = false;
            } else {
                try {
                    FileInfo file = new FileInfo(txtFilename.Text);
                    if(!file.Exists) {
                        DialogBox.ShowAlert(this, "File does not exist", "Error");
                        valid = false;
                    }
                } catch(Exception ex) {
                    DialogBox.ShowAlert(this, ex.Message, "Error");
                    valid = false;
                }
            }
            return valid;
        }

        /// <summary>
        /// Detects the type (zip/xml) of uploaded file and parses it to an XmlDataDocument
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void PromptSelectComponents() {
            try {
                FileInfo file = new FileInfo(txtFilename.Text);
                string ext = file.Extension.ToLowerInvariant();
                string innerExtension = string.Empty; // the file within the zip
                files = new Dictionary<string, string>();
                StringBuilder fileBuffer = new StringBuilder();
                using(MemoryStream ms = new MemoryStream()) {
                    string selectedType = (lstType.SelectedItem as ListBoxItem).Tag.ToString();
                    if(ext == ".zip") {
                        ZipInputStream zipStream = new ZipInputStream(file.OpenRead());
                        ZipEntry entry;
                        byte[] buffer = new byte[1024];
                        while((entry = zipStream.GetNextEntry()) != null) {
                            fileBuffer.Remove(0, fileBuffer.Length);
                            StreamReader reader = new StreamReader(zipStream);
                            string fileContents = reader.ReadToEnd();
                            files.Add(entry.Name, fileContents);
                            if(innerExtension == string.Empty) { // first file, use it's extension to determine the import type
                                if(selectedType == "auto") {
                                    string detectedExt = System.IO.Path.GetExtension(entry.Name);
                                    if(detectedExt.Length > 1) {
                                        if(detectedExt == ".xml") {
                                            innerExtension = DetectXMLType(new KeyValuePair<string, string>(entry.Name, fileContents));
                                        } else {
                                            innerExtension = detectedExt.ToLowerInvariant().Substring(1); // use the extension of the first file in the archive
                                        }
                                    }
                                } else {
                                    innerExtension = selectedType;
                                }
                            }
                        }
                    } else {
                        using(StreamReader reader = new StreamReader(file.OpenRead())) {
                            files.Add(file.Name, reader.ReadToEnd().Replace("\r", ""));
                        }
                        if(selectedType == "auto") {
                            if(ext == ".xml") {
                                innerExtension = DetectXMLType(files.First());
                            } else {
                                innerExtension = ext.ToLowerInvariant().Substring(1);
                            }
                        } else {
                            innerExtension = selectedType;
                        }
                    }
                }
                if(innerExtension.Length == 0) {
                    throw new Exception("Could not detect file type");
                }
                if(files.Count == 0) {
                    throw new Exception("No files found");
                }

                switch(innerExtension) {
                    case "xml":
                        tbXML.IsSelected = true;
                        this.type = Constants.DataType.XML;
                        break;
                    case "xmlxls":
                        tbXMLXLS.IsSelected = true;
                        this.type = Constants.DataType.XMLXLS;
                        break;
                    case "properties":
                        this.type = Constants.DataType.Properties;
                        tbResources.IsSelected = true;//todo: make all numbers bold in textblock
                        txtBundleSummary.Text = string.Format("Uploaded {0} {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "bundles" : "bundle", files.Count == 1 ? "this bundle" : "these bundles");
                        break;
                    case "resx":
                        this.type = Constants.DataType.Resx;
                        tbResources.IsSelected = true;
                        txtBundleSummary.Text = string.Format("Uploaded {0} {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "bundles" : "bundle", files.Count == 1 ? "this bundle" : "these bundles");
                        break;
                    case "po":// todo: make this parse subdirs for multiple bundles
                        this.type = Constants.DataType.PO;
                        tbResources.IsSelected = true;
                        txtBundleSummary.Text = string.Format("Uploaded {0} {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "bundles" : "bundle", files.Count == 1 ? "this bundle" : "these bundles");
                        break;
                    default:
                        throw new Exception("Unsupported file type - " + innerExtension);
                }
                txtProgress.Inlines.Clear();
            } catch(Exception ex) {
                DialogBox.ShowAlert(this, "Error #456: "+ex.Message, "Error");
            }
        }

        /// <summary>
        /// Detects whether the XML file is an XML spreadsheet or a ResourceBlender backup.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        private string DetectXMLType(KeyValuePair<string, string> file) {
            string type = string.Empty;
            try {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(file.Value);
                XmlNodeList generatorNodes = doc.GetElementsByTagName("generator");
                if(generatorNodes.Count > 0 && generatorNodes.Item(0).InnerText.ToLower().Contains("resourceblender")) {
                    type = "xml"; // document contains a <generator>resourceblender... </generator> node...
                } else {
                    type = "xmlxls"; // xml spreadsheet
                }
            } catch {
                type = file.Key.ToLowerInvariant().Substring(1);
            }
            return type;
        }

        /// <summary>
        /// Imports using a background thread.
        /// </summary>
        private void DoImport() {
            try {
                bool includeApps = chkApplications.IsChecked == true,
                     includeElements = chkElements.IsChecked == true,
                     includeResources = chkResources.IsChecked == true,
                     includeUsers = false,
                     includeTemplates = chkTemplates.IsChecked == true,
                     clearDatabase = chkClearDatabase.IsChecked == true;
                RBApplication selectedApplication = lstApplications.SelectedValue as RBApplication;
                worker = new BackgroundWorker();
                worker.WorkerReportsProgress = true;
                worker.WorkerSupportsCancellation = true;
                worker.ProgressChanged += (s, pce) => {
                    if(pce.UserState is StatusMessage) {
                        StatusMessage message = pce.UserState as StatusMessage;
                        LogText(message.Message, message.MessageType);
                    }
                };
                worker.DoWork += (s, dwe) => {
                    StatusMessage.StatusChangedEventHandler logHandler = new StatusMessage.StatusChangedEventHandler((statusMessage) => {
                        worker.ReportProgress(0, statusMessage);
                    });
                    try {
                        switch(type) {
                            case Constants.DataType.XML:
                                XmlDocument doc = new XmlDocument();
                                doc.LoadXml(files.First().Value);
                                ResourceImporter.ImportXMLBackup(logHandler, doc, clearDatabase, includeApps, includeElements, includeResources, includeUsers, includeTemplates);
                                break;
                            case Constants.DataType.XMLXLS:
                                ResourceImporter.ImportXMLSpreadsheet(logHandler, files.First().Value);
                                break;
                            case Constants.DataType.PO:
                                ResourceImporter.ImportPO(logHandler, files, selectedApplication);
                                break;
                            case Constants.DataType.Properties:
                                ResourceImporter.ImportProperties(logHandler, files, selectedApplication);
                                break;
                            case Constants.DataType.Resx:
                                ResourceImporter.ImportResx(logHandler, files, selectedApplication);
                                break;
                        }
                    } catch(Exception ex) {
                        dwe.Result = ex.Message;
                    }
                };
                worker.RunWorkerCompleted += (s, rwe) => {
                    if(rwe.Result != null && rwe.Result is string) {
                        DialogBox.ShowAlert(this, rwe.Result.ToString(), "Error");
                    }
                    prgProgress.Visibility = Visibility.Hidden;
                    btnAbort.Visibility = Visibility.Hidden;
                    btnNext.Content = "Close";
                    txtProgress.Focus();
                };
                txtProgress.Inlines.Clear();
                prgProgress.Visibility = Visibility.Visible;
                //btnAbort.Visibility = Visibility.Visible; // todo: fix abort button?
                worker.RunWorkerAsync();
            } catch(Exception ex) {
                LogText("Error importing #458: " + ex.Message, StatusMessage.StatusMessageType.Error);
                prgProgress.Visibility = Visibility.Hidden;
                btnNext.Content = "Close";
            }
        }

        /// <summary>
        /// Determines whether the background thread can be aborted
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteAbort(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = worker != null && worker.IsBusy && !worker.CancellationPending;
        }

        /// <summary>
        /// Aborts an import.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void AbortImport(object sender, RoutedEventArgs e) {
            if(worker != null && worker.IsBusy && worker.WorkerSupportsCancellation) {
                try {
                    worker.CancelAsync();
                } catch { }
            }
        }

        /// <summary>
        /// Appends text to the log textbox.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="error"><c>true</c> if the text is an error.</param>
        private void LogText(string text, StatusMessage.StatusMessageType type) {
            Run run = new Run(text + "\n");
            switch(type) {
                case StatusMessage.StatusMessageType.Error:
                    run.Style = styError;
                    break;
                case StatusMessage.StatusMessageType.Success:
                    run.Style = stySuccess;
                    break;
                case StatusMessage.StatusMessageType.Status:
                default:
                    run.Style = styStatus;
                    break;
            }
            txtProgress.Inlines.Add(new Span(run));
        }

        /// <summary>
        /// Sets the text on the status message (null hides the statusbar and label).
        /// </summary>
        /// <param name="message">The message.</param>
        private void SetStatus(string message) {
            if(message == null) {
                prgStatus.Visibility = Visibility.Hidden;
                lblStatus.Visibility = Visibility.Hidden;
            } else {
                prgStatus.Visibility = Visibility.Visible;
                lblStatus.Visibility = Visibility.Visible;
                lblStatus.Content = message;
            }
        }
    }
}